package	model.code;

import event.ListAddEvent;
import event.ListRemoveEvent;
import model.symbol.Scope;
import model.symbol.Symbol;
import model.code.line.LineList;
import observer.ListObserver;
import observer.ObservedList;
import observer.ObservedString;

import java.util.ArrayList;

/**
 * This class is for organizational sections of code inside {@link CodeFile}s,
 * and generally anything containing {@link CodeFileChunk}s, including {@link CodeFile}s themselves.
 *
 * @author Neil Dickson
 */
public class Section extends CodeFileChunk implements Symbol, ListObserver<CodeFileChunk> {
	private	ObservedList<CodeFileChunk>	content;
	private	ObservedList<Rowable>		seeAlso;
	private	ObservedString				segment;

	public Section(String name, String description) {
		super(name, description);
		seeAlso = new ObservedList<Rowable>();
		content = new ObservedList<CodeFileChunk>();
		content.addObserver(this);
		segment = new ObservedString();
	}

	public ObservedList<Rowable> getSeeAlso() {
		return seeAlso;
	}

	public ObservedList<CodeFileChunk> getContent() {
		return content;
	}

	public ObservedString getSegmentName() {
		return segment;
	}

	public ListAddEvent<CodeFileChunk> addAfter(CodeFileChunk chunk, CodeFileChunk previous) {
		int index = -1;
		if (previous!=null) {
			index = content.indexOf(previous);
		}
		return add(chunk,index+1);
	}

	public ListAddEvent<CodeFileChunk> add(CodeFileChunk chunk,int index) {
		chunk.setParent(this);
		content.add(index,chunk,this);
		Scope globalScope = getGlobalScope();
		if (globalScope!=null && !globalScope.contains(chunk)) {
			addToGlobalScope(chunk);
			if (globalScope.isNotificationEnabled()) {
				if (chunk instanceof Function) {
					((Function)chunk).nowHaveGlobalScope();
				}
				else if (chunk instanceof GlobalVariable) {
					((GlobalVariable)chunk).nowHaveGlobalScope();
				}
				else if (chunk instanceof Structure) {
					((Structure)chunk).nowHaveGlobalScope();
				}
				else if (chunk instanceof Constants) {
					((Constants)chunk).nowHaveGlobalScope();
				}
			}
		}
		return new ListAddEvent<CodeFileChunk>(content,index);
	}
	public ListAddEvent<CodeFileChunk> add(CodeFileChunk chunk) {
		return add(chunk,content.size());
	}

	public ListRemoveEvent<CodeFileChunk> remove(int index) {
		CodeFileChunk chunk = content.get(index);
		chunk.setParent(null);
		content.remove(index,this);
		removeFromGlobalScope(chunk);
		return new ListRemoveEvent<CodeFileChunk>(content,chunk,index);
	}
	public ListRemoveEvent<CodeFileChunk> remove(CodeFileChunk chunk) {
		return remove(content.indexOf(chunk));
	}

	public int size() {
		return content.size();
	}

	public CodeFileChunk get(int index) {
		return content.get(index);
	}

	public CodeFileChunk getLast() {
		return (content.size()>0) ? content.get(content.size()-1) : null;
	}

	public int indexOf(CodeFileChunk chunk) {
		return content.indexOf(chunk);
	}

	public ArrayList<Symbol> getAllGlobalSymbols() {
		ArrayList<Symbol> list = new ArrayList<Symbol>();
		for (CodeFileChunk chunk : content) {
			if (chunk instanceof Section) {
				list.addAll(((Section)chunk).getAllGlobalSymbols());
			}
			else if (chunk instanceof Constants) {
				list.addAll(((Constants)chunk).getAllGlobalSymbols());
			}
			else if (chunk instanceof Function) {
				list.add(chunk);
			}
			else if (chunk instanceof Structure) {
				list.add(chunk);
			}
			else if (chunk instanceof GlobalVariable) {
				list.add(chunk);
			}
		}
		return list;
	}

	public void listElementAdded(ObservedList<CodeFileChunk> s, int index) {
		CodeFileChunk chunk = s.get(index);
		chunk.setParent(this);
		addToGlobalScope(chunk);
	}

	public void listElementRemoved(ObservedList<CodeFileChunk> s, int index, CodeFileChunk element) {
		element.setParent(null);
		removeFromGlobalScope(element);
	}

	public void listElementSet(ObservedList<CodeFileChunk> s, int index, CodeFileChunk oldElement) {
		oldElement.setParent(null);
		removeFromGlobalScope(oldElement);
		CodeFileChunk newChunk = s.get(index);
		newChunk.setParent(this);
		addToGlobalScope(newChunk);
	}

	public void listElementMoved(ObservedList<CodeFileChunk> s, int from, int to) {
		// No change is required here
	}

	public Section copy() {
		Section that = new Section(getName().toString(),getDescription().toString());
		// addAll() is used instead of = so that the parents of the elements are set appropriately
		// and that remains an observer of its content
		// NOTE: Actually, a Section can't copy its content for the "Ctrl+C" copy operation, because anything inside it is copied separately and inserted on paste.
//		that.content.addAll(content.copy());
		that.seeAlso.ensureCapacity(seeAlso.size());
		for (Rowable sa : seeAlso) {
			that.seeAlso.add(sa.copy(that,that.seeAlso.size()));
		}
		that.segment = segment.copy();
		return that;
	}

	public CodeFile getParent() {
		return (CodeFile)super.getParent();
	}

	public LineList getLineList(int locType, int locIndex) {
		int row = locIndex>>>16;
		int col = locIndex&0xFFFF;
		ObservedList<? extends Rowable> table = null;
		if (locType==LOCATION_SEE_ALSO) {
			table = seeAlso;
		}
		else if (locType==LOCATION_SEGMENT) {
			// TODO: Implement this if segment is ever indicated with a LineList.
		}
		else {
			return super.getLineList(locType, locIndex);
		}
		if (table!=null && table.size()>row && table.get(row).getStrings().length>col) {
			return table.get(row).getStrings()[col];
		}
		return null;
	}

	public LineList getNextLineList(LineList list) {
		int locType = list.getLocationType();
		int locIndex = list.getLocationIndex();
		if (locType==LOCATION_NAME) {
			return description;
		}
		if (locType==LOCATION_DESCRIPTION) {
			if (getSeeAlso().size()>0) {
				return getLineList(LOCATION_SEE_ALSO,0);
			}
			return null;
		}
		int row = locIndex>>>16;
		int col = locIndex&0xFFFF;
		if (locType==LOCATION_SEE_ALSO) {
			ObservedList<? extends Rowable> table = getSeeAlso();
			if (col+1<table.get(row).getSize()) {
				return getLineList(locType,locIndex+1);
			}
			if (row+1<table.size()) {
				return getLineList(locType,(row+1)<<16);
			}
		}
		return null;
	}

	public LineList getPrevLineList(LineList list) {
		int locType = list.getLocationType();
		int locIndex = list.getLocationIndex();
		if (locType==LOCATION_NAME) {
			return null;
		}
		if (locType==LOCATION_DESCRIPTION) {
			return name;
		}
		int row = locIndex>>>16;
		int col = locIndex&0xFFFF;
		if (locType==LOCATION_SEE_ALSO) {
			if (col-1>=0) {
				return getLineList(locType,locIndex-1);
			}
			if (row-1>=0) {
				return getLineList(locType,(row-1)<<16);
			}
			return description;
		}
		return null;
	}

	public LineList getLastLineList() {
		if (getSeeAlso().size()>0) {
			return getSeeAlso().getLast().getStrings()[getSeeAlso().getLast().getSize()-1];
		}
		return description;
	}
}
